package com.daylightrobbery.server.handler;


import com.daylightrobbery.entity.Account;
import com.daylightrobbery.entity.UserSignInLog;
import com.daylightrobbery.server.GameManager;
import com.daylightrobbery.server.entity.PlayerInfo;
import com.daylightrobbery.server.entity.Room;
import com.daylightrobbery.server.packet.game.GamePrepareDataPacket;
import com.daylightrobbery.server.packet.game.player.PlayerInfoSyncPacket;
import com.daylightrobbery.server.packet.game.player.ResyncPlayerDataPacket;
import com.daylightrobbery.server.packet.login.*;
import com.daylightrobbery.server.packet.type.DataPacket;
import com.daylightrobbery.server.packet.DataPacketFactory;
import com.daylightrobbery.server.packet.GamePacket;
import com.daylightrobbery.server.packet.type.MulticastDataPacket;
import com.daylightrobbery.server.packet.type.RoomDataPacket;
import com.daylightrobbery.server.packet.type.ShardingDataPacket;
import com.daylightrobbery.service.UserSignInLogService;
import com.daylightrobbery.utils.ObjectMapperUtil;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.channel.socket.DatagramPacket;
import io.netty.util.CharsetUtil;
import io.netty.util.ReferenceCountUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;

import java.net.InetSocketAddress;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;

@Component
public class UdpServerHandler extends SimpleChannelInboundHandler<DatagramPacket> implements ApplicationContextAware {


    private static final Logger log = LoggerFactory.getLogger(UdpServerHandler.class);
    private ApplicationContext applicationContext;
    private HashMap<String, AtomicLong> packetIdGeneratorMap = new HashMap<>();

    private AtomicLong packetId = new AtomicLong(0L);
    private static ConcurrentHashMap<Long, GamePacket> waitAckPacket = new ConcurrentHashMap<>();

    private final static String SERVER_UID = "-1";
    private final static Integer TIMEOUT = 5;
    private ChannelHandlerContext ctx;
    @Autowired
    private DataPacketFactory dataPacketFactory;

    @Autowired
    private UserSignInLogService userSignInLogService;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        this.ctx = ctx;
        super.channelActive(ctx);
    }

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, DatagramPacket packet) {
        ByteBuf data = packet.content();
        String received = data.toString(CharsetUtil.UTF_8);
        GamePacket gamePacket = ObjectMapperUtil.toObject(received, GamePacket.class);
        if(gamePacket.getType() != new IdleReqPacket().getType() && gamePacket.getType() != 0 && gamePacket.getType() != ShardingDataPacket.SHARDING_TYPE){
            log.info("Received message from[" + packet.sender().getHostString() + "]: " + received);
        }
        if(gamePacket.getType() == new LoginUdpServerReqPacket().getType() || gamePacket.getType() == GamePacket.ACK_TYPE
        || gamePacket.getType() == new SignUpReqPacket().getType()){
            processGamePacket(packet, gamePacket);
            return;
        }
        List<GamePacket> gamePackets = PacketManager.processGamePackets(gamePacket);
        for (GamePacket gp : gamePackets) {
            processGamePacket(packet, gp);
        }
    }

    public void processGamePacket(DatagramPacket packet, GamePacket gamePacket){
        String dstUid = "";
        // 这应该是个对服务器主动请求的ack包
        if(gamePacket.getUid() != null && gamePacket.getUid().equals(SERVER_UID)){
            GamePacket remove = waitAckPacket.remove(gamePacket.getId());//移除缓冲区的包即可
            if (remove == null) {
                return;
            }
            //log.info("移除缓冲区数据包: " + ObjectMapperUtil.toJSON(remove));
            return;
        }

        DataPacket dataPacket = dataPacketFactory.getDataPacketsByType(gamePacket);

        // 构造回复信息, 返回type为ack的应答包
        GamePacket ack = gamePacket.Ack();
        send(ack.getUid(), ack, packet.sender(), false, true);

        // 异步处理数据包，然后同步给需要的客户端
        ctx.channel().eventLoop().execute(()->{
            DataPacket finishedDataPacket = dataPacket.process();
            if(finishedDataPacket instanceof MulticastDataPacket){
                // 需要多播
                if(finishedDataPacket instanceof RoomDataPacket){
                    RoomDataPacket roomDataPacket = (RoomDataPacket) finishedDataPacket;
                    int roomId = roomDataPacket.getRoomId();
                    Room room = GameManager.getInstance().getRoomById(roomId);
                    for (Map.Entry<String, PlayerInfo> player : room.getPlayers().entrySet()) {
                        GamePacket finishedGamePacket = new GamePacket()
                                .setId(PacketManager.generateId(player.getKey()))
                                .setType(finishedDataPacket.getType())
                                .setUid(SERVER_UID)
                                .setData(finishedDataPacket);
                        send(player.getKey(), finishedGamePacket, GameManager.getInstance().getUserAddress(player.getKey()));
                    }
                    if(room.getRoomOwnerUid() == null){
                        // 需要关闭这个房间
                        GameManager.getInstance().removeRoom(roomId);
                    }
                }
            }else{
                String uid = "";
                // 原路返回
                GamePacket finishedGamePacket = new GamePacket()
                        .setType(finishedDataPacket.getType())
                        .setUid(SERVER_UID)
                        .setData(finishedDataPacket);
                // 如果是登录Udp服务器的包则记录用户登录地址, 刷新id生成器
                if(dataPacket instanceof LoginUdpServerReqPacket && finishedDataPacket instanceof LoginUdpServerRespPacket) {
                    if (((LoginUdpServerRespPacket) finishedDataPacket).getMsg().equals(LoginUdpServerRespPacket.SUCCESS)) {
                        uid = ((LoginUdpServerRespPacket) finishedDataPacket).getUid();
                        String account = ((LoginUdpServerRespPacket) finishedDataPacket).getAccount();
                        PacketManager.register(uid, 1); // 同步序列号
                        GameManager.getInstance().recordUserAddress(uid, packet.sender());
                        finishedGamePacket.setId(PacketManager.generateId(uid));
                        log.info("account:[" + account + "]" + " uid:[ " + uid + "] 已上线, 来自:[" + packet.sender().toString() + "]");
                        // 记录登录日志
                        UserSignInLog log = new UserSignInLog();
                        Account logAccount = new Account()
                                .setUid(uid)
                                .setAccount(account);
                        try {
                            userSignInLogService.addLog(
                                    logAccount,
                                    packet.sender().getHostString(),
                                    ((LoginUdpServerReqPacket) dataPacket).getDeviceInfo(),
                                    UserSignInLog.ONLINE
                            );
                        }catch (Exception e){
                            e.printStackTrace();
                        }
                        Integer roomId = GameManager.getInstance().getPlayerInGameRoomId(uid);
                        if (roomId != null) {
                            Room room = GameManager.getInstance().getRoomById(roomId);
                            GamePrepareDataPacket initDataPacket = room.getPacket();
                            GamePacket initGamePacket = new GamePacket()
                                    .setId(PacketManager.generateId(uid))
                                    .setType(initDataPacket.getType())
                                    .setUid(SERVER_UID)
                                    .setData(initDataPacket);
                            send(uid, finishedGamePacket, packet.sender());
                            send(uid, initGamePacket, packet.sender());

                            return;
                        }
                    }
                    else{
                        finishedGamePacket.setId(0L);
                    }
                    send(uid, finishedGamePacket, packet.sender());
                }
                else if(finishedDataPacket instanceof SignUpRespPacket){
                    finishedGamePacket.setId(0L);
                    send(uid, finishedGamePacket, packet.sender());
                }
                else if(finishedDataPacket instanceof ResyncPlayerDataPacket){
                    ResyncPlayerDataPacket p = (ResyncPlayerDataPacket)finishedDataPacket;
                    int roomId = p.roomId;
                    uid = p.uid;
                    List<PlayerInfoSyncPacket> packets = GameManager.getInstance().getAllPlayerSyncPackets(roomId);
                    for (PlayerInfoSyncPacket playerInfoSyncPacket : packets) {
                        if(playerInfoSyncPacket == null){
                            continue;
                        }
                        GamePacket playerGamePacket = new GamePacket()
                                .setId(PacketManager.generateId(uid))
                                .setType(playerInfoSyncPacket.getType())
                                .setUid(SERVER_UID)
                                .setData(playerInfoSyncPacket);
                        send(uid, playerGamePacket, packet.sender());
                    }
                }
                else{
                    uid  = gamePacket.getUid();
                    finishedGamePacket.setId(PacketManager.generateId(uid));
                    send(uid, finishedGamePacket, packet.sender());
                }
                if(dataPacket instanceof IdleReqPacket){
                    GameManager.getInstance().recordUserAddress(((IdleReqPacket) dataPacket).uid, packet.sender());
                }

            }

        });

    }

    public void send(String dstUid, GamePacket packet, InetSocketAddress recipient){
        send(dstUid, packet, recipient, true, false);
    }

    public void send(String dstUid,GamePacket packet, InetSocketAddress recipient, boolean waitAck, boolean skipSharding){
        if(packet.getType() != ShardingDataPacket.SHARDING_TYPE && packet.getType() != new IdleRespPacket().getType() && packet.getType() != 0){
            log.info("Server Send to[" + recipient.getHostString() + ":" +  recipient.getPort() + "]: " + ObjectMapperUtil.toJSON(packet));
        }
//        if(packet.getType() == new RoundFinishedPacket().getType()){
//            RoundFinishedPacket p = ObjectMapperUtil.toObject(packet.getData(), RoundFinishedPacket.class);
//            PlayerInfo nextPlayer = GameManager.getInstance().getRoomById(p.getRoomId()).getPlayers().get(p.getUid());
//            log.info("[回合结束]: 轮到玩家[" + nextPlayer.getAccount() + "]");
//        }
        String gamePacketJson = ObjectMapperUtil.toJSON(packet);

        if(!skipSharding){
            List<ShardingDataPacket> packets = ShardingDataPacket.splitPacket(packet);
            for (int i = 0; i < packets.size(); i++) {
                GamePacket shardingPacket = new GamePacket();
                if(i == 0){
                    shardingPacket.setId(packet.getId());
                }else{
                    shardingPacket.setId(PacketManager.generateId(dstUid));
                }
                shardingPacket
                        .setUid(packet.getUid())
                        .setData(packets.get(i))
                        .setType(ShardingDataPacket.SHARDING_TYPE);
                send(dstUid, shardingPacket, recipient, waitAck, true);
            }
            return;
        }

        // ByteBuf buf = new PooledByteBufAllocator().directBuffer(gamePacketJson.length());
        ByteBuf buf = ctx.alloc().buffer(gamePacketJson.length());
        buf.writeBytes(gamePacketJson.getBytes(CharsetUtil.UTF_8));
        // 使用addListener来在发送完成后正确释放ByteBuf
        ctx.writeAndFlush(new DatagramPacket(buf, recipient)).addListener(future -> {
            // 仅当操作成功完成后释放ByteBuf
            if (future.isSuccess()) {
                if(ReferenceCountUtil.refCnt(buf) != 0){
                    //ReferenceCountUtil.release(buf);
                }
                //ReferenceCountUtil.release(buf);
            } else {
                // 处理发送失败的情况，仅做记录，重传在其他地方
                log.error("Failed to send packet to " + recipient.toString(), future.cause());
            }
        });
        if(waitAck){
            waitAckPacket.put(packet.getId(), packet);
            this.ctx.channel().eventLoop().schedule(()->{
                GamePacket retryPacket = waitAckPacket.get(packet.getId());
                if(retryPacket == null){
                    return;
                }
                send(dstUid,retryPacket, recipient, true, skipSharding); // 需要避免重复等待确认，可能导致无限重试
            }, TIMEOUT, TimeUnit.SECONDS);
        }
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        cause.printStackTrace();
        ctx.close();
    }

}
